#include "daemon.h"

#include <QString>
#include <QNetworkAccessManager>
#include <QDataStream>
#include <QFile>

#include "config.h"
#include "disk.h"
#include "sender.h"
#include "receiver.h"
#include "action.h"
#include "avdfile.h"
#include "communicator.h"
#include "filesystemwatcher.h"
#include "fileerror.h"
#include "foldererror.h"

#include <base64/base64.h>

#include <iostream> //debug
#include <fstream>
using std::cout;
using std::cerr;
using std::endl;
using std::ifstream;
using std::ios;
using std::exception;

/**
 * Helper function
 */
#define connectDaemon(type, ...) \
	connect(receiver, SIGNAL(sig##type(unsigned int,## __VA_ARGS__)), this, SLOT(slot##type(unsigned int,## __VA_ARGS__)))

namespace Avd {

Daemon::Daemon(const QString& configFileName) throw(DaemonError): configFileName(configFileName), isConnected(false), session(0) {
	config = new Config(configFileName);
	try {
		config->load();
	}
	catch(XmlError& e) {
		throw(DaemonError("Daemon::Daemon(const QString& configFileName): constructor failed trying to load config"));
	}

	disk = new Disk(config->getDisk(), this);
	sender = new Sender(this, config->getServerUrl(), config->getPort());
	receiver = new Receiver();

	timer = new QTimer();
	connect(timer, SIGNAL(timeout()), this, SLOT(updateDisk()));
	timer->start(1000 * 360);
	fs = 0;
//	try {
//		fs = new FileSystemWatcher(this);
//		fs->add(Folder(""));
//		fs->start();
//	}
//	catch(InotifyException& e) {
//		cerr << "Inotify exception occured: " << e.GetMessage() << endl;
//		delete fs;
//		fs = 0;
//	}
	connect(sender, SIGNAL(sigReply(QNetworkReply*)), receiver, SLOT(receive(QNetworkReply*)));
	
	connectDaemon(Login, unsigned int);
	connectDaemon(Logoff);
	connectDaemon(Download, File&, unsigned int, unsigned int);
	connectDaemon(Upload, File&);
	connectDaemon(List, const QString&, QList<File>, QList<Folder>);
	connectDaemon(Move, const QString&, const QString&);
	connectDaemon(Delete, QString&);
	connectDaemon(CreateFolder, const Folder&);
	connectDaemon(Copy, const QString&, const File&);
	connectDaemon(Version, const File&);
}

Daemon::~Daemon() {
	if(config) delete config;
	if(disk) delete disk;
	if(sender) delete sender;
	if(receiver) delete receiver;

}

void Daemon::askLogin() throw(SenderError) {
	sender->askLogin(config->getUser(), config->getPassword());
}

void Daemon::askLogoff() throw(SenderError) {
	sender->askLogoff(session, config->getUser());
}

void Daemon::askDownload(const File& file) throw(SenderError) {
	if(file.getSize() > 5000) {
		sender->askDownload(session, file.getPath(), file.getName(), 1, 10);
	} else {
		sender->askDownload(session, file.getPath(), file.getName());
	}
}

void Daemon::askUpload(File& file) throw(FileError, SenderError) {
	try {
		sender->askUpload(session, file.getPath(), file.getName(), file.getVersion());
	} catch(FileError& e) {
		throw FileError(string(e.what()), file);
	}
}

void Daemon::askList(const Folder& folder) throw(SenderError) {
	sender->askList(session, folder.getFullPath());
}

void Daemon::askMove(const QString& oldPath, const QString& oldName,
										 const QString& newPath, const QString& newName) throw(SenderError) {
	sender->askMove(session, oldPath, oldName, newPath, newName);
}

void Daemon::askDelete(const File& file) throw(SenderError) {
	sender->askDelete(session, file.getPath(), file.getName());
}

void Daemon::askDelete(const Folder& folder) throw(SenderError) {
	sender->askDelete(session, folder.getPath(), folder.getName());
}

void Daemon::askCreateFolder(const Folder& folder) throw(SenderError) {
	sender->askCreateFolder(session, folder.getPath(), folder.getName());
}

void Daemon::askCopy(const File& file, const Folder& copyFolder) throw(SenderError) {
	sender->askCopy(session, file.getPath(), file.getName(), copyFolder.getFullPath());
}

void Daemon::askCopy(const Folder& folder, const Folder& copyFolder) throw(SenderError) {
	sender->askCopy(session, folder.getPath(), folder.getName(), copyFolder.getFullPath());
}

void Daemon::askVersion(const QString& path, const QString& name) throw(SenderError) {
	sender->askVersion(session, path, name);
}

bool Daemon::connected() const {
	return isConnected;
}

void Daemon::slotLogin(unsigned int status, unsigned int _session) {
	try {
		switch(status) {
			case 0: {
				isConnected = true;
				this->session = _session;
				cout << "Avd connected." << endl;
				updateDisk();
				break;
			}
			case 1:
				throw DaemonError("void Daemon::slotLogin(unsigned int status, unsigned int _session): incorrect user or password.", status);
				break;
			case 2:
				throw DaemonError("void Daemon::slotLogin(unsigned int status, unsigned int _session): user already logged in.", status);
				break;
			case 3:
				throw DaemonError("void Daemon::slotLogin(unsigned int status, unsigned int _session): unknown error.", status);
				break;
			default:
				throw DaemonError("void Daemon::slotLogin(unsigned int status, unsigned int _session): unknown exception.", status);
		}
	}
	catch(DaemonError& e) {
		cerr << "DaemonError exception occured: " << e.what() << "Status: " << e.getStatus() << endl;
	}
	catch(FileSystemError& e) {
		cerr << "FileSystemError exception occured: " << e.what()
				 << disk->getAbsolutePath().toStdString() << " cannot be checked. " << endl;
	}
	catch(exception& e) {
		cerr << "Unknown exception occured: " << e.what() << endl;
	}
}
void Daemon::slotLogoff(unsigned int status) {
	try {
		switch(status) {
			case 0:
				isConnected = false;
				session = 0;
				cout << "Avd disconnected." << endl;
				break;
			case 1:
				isConnected = false;
				session = 0;
				throw DaemonError("void Daemon::slotLogoff(unsigned int status): you are not connected.", status);
				break;
			case 2:
				throw DaemonError("void Daemon::slotLogoff(unsigned int status): unknown error.", status);
				break;
			default:
				throw DaemonError("void Daemon::slotLogoff(unsigned int status): unknown exception.", status);
				break;
			}
	}
	catch(DaemonError& e) {
		cerr << "DaemonError exception occured: " << e.what() << "Status: " << e.getStatus() << endl;
	}
	catch(exception& e) {
		cerr << "Unknown exception occured: " << e.what() << endl;
	}
}

void Daemon::slotDownload(unsigned int status, File& file,
													const unsigned int part, const unsigned int totalParts) {
	try {
		switch(status) {
			case 0: {
				if(part != 0 && totalParts > 1) {
					if(part < totalParts) {
						sender->askDownload(session, file.getPath(), file.getName(), part+1, totalParts);
					}
					file.setTemp(part, totalParts);
				}
				disk->update(file);
				cout << file.getName().toStdString() << " downloaded sucessfully." << endl;
				break;
			}
			case 1:
				throw DaemonError("void Daemon::slotDownload(unsigned int status, File& file): Download error. You are not connected.", status);
				break;
			case 2:
				throw DaemonError(string("void Daemon::slotDownload(unsigned int status, File& file): ")
													+ file.getName().toStdString()
													+ string(" does not exist."), status);
				break;
			case 3:
				throw DaemonError("void Daemon::slotDownload(unsigned int status, File& file): unknown server error.", status);
				break;
			default:
				throw DaemonError("void Daemon::slotDownload(unsigned int status, File& file): unknown exception.", status);
				break;
		}
	}
	catch(DaemonError& e) {
		cerr << "DaemonError exception occured: " << e.what() << "Status: " << e.getStatus() << endl;
	}
	catch(FileError& e) {
		cerr << "FileError exception occured: " << e.what()
				 << e.getFile().getFullPath().toStdString() << " was not updated. " << endl;
	}
	catch(FileSystemError& e) {
		cerr << e.what() << endl;
	}
	catch(exception& e) {
		cerr << "Unknown exception occured: " << e.what() << endl;
	}
}

void Daemon::slotUpload(unsigned int status, File& file) {
	try {
		switch(status) {
			case 0: {
				disk->update(file, false);
//				cout << file.getName().toStdString() << " updated successfully" << endl;
				break;
			}
			case 1:
				throw DaemonError(string("void Daemon::slotUpload(unsigned int status, File& file): cannot update ")
													+ file.getName().toStdString()
													+ string(". Are you authenticated?"), status);

				break;
			case 2:
				throw DaemonError(string("void Daemon::slotUpload(unsigned int status, File& file): cannot update ")
													+ file.getName().toStdString()
													+ string(". Higher version at server."), status);
				break;
			case 3:
				throw DaemonError(string("void Daemon::slotUpload(unsigned int status, File& file): cannot update ")
													+ file.getName().toStdString()
													+ string(". Server unknown error."), status);
				break;
			default:
				throw DaemonError("void Daemon::slotUpload(unsigned int status, File& file): unknown exception", status);
				break;
		}
	}
	catch(DaemonError& e) {
		cerr << "DaemonError exception occured: " << e.what() << "Status: " << e.getStatus() << endl;
	}
	catch(FileError& e) {
		cerr << "FileError exception occured: " << e.what()
				 << e.getFile().getFullPath().toStdString() << " was not updated. " << endl;
	}
	catch(exception& e) {
		cerr << "Unknown exception occured: " << e.what() << endl;
	}
}
void Daemon::slotList(unsigned int status, const QString& path, QList<File> files, QList<Folder> folders) {
	try {
		switch(status) {
			case 0: {
				QString nonConstPath = path;
				Folder f(nonConstPath);
				AvdFile tbFile(disk->getAbsolutePath(), f.getFullPath());
				QList<Action*> actions = tbFile.check();
				actions += tbFile.update(files, folders);
				foreach(Action* a, actions) {
					try {
						a->doIt(this); //doIt returns a bool.
					}
					catch(SenderError& e) {
						cerr << "SenderError exception occured: " << e.what() << endl;
						cerr << "SenderError XML:\n" << e.getXml().toStdString() << endl;
					}
					catch(FileError& e) {
						cerr << "FileError exception occured: " <<  e.what()
								 << "File: " << e.getFile().getFullPath().toStdString() << endl;
					}
					catch(FolderError& e) {
						cerr << "FolderError exception occured: " << e.what()
								 << "Folder: " << e.getFolder().getFullPath().toStdString() << endl;
					}
				}
				while (!actions.isEmpty()){
					delete actions.takeFirst();
				}
				break;
			}
			case 1:
				throw DaemonError(string("void Daemon::slotList(unsigned int status, const QString& path, QList<File> files, QList<Folder> folders): cannot list ")
													+ path.toStdString()
													+ string(". Are you authenticated?"), status);
				break;
			default:
				throw DaemonError("void Daemon::slotList(unsigned int status, const QString& path, QList<File> files, QList<Folder> folders): unknown error.", status);
				break;
		}
	}
	catch(DaemonError& e) {
		cerr << "DaemonError exception occured: " << e.what() << "Status: " << e.getStatus() << endl;
	}
	catch(FileSystemError& e) {
		cerr << "FileSystemError exception occured: " << e.what()
				 << disk->getAbsolutePath().toStdString() << " cannot be checked. " << endl;
	}
	catch(exception& e) {
		cerr << "Unknown exception occured: " << e.what() << endl;
	}
}

void Daemon::slotDelete(unsigned int status, QString& fullpath) {
	try {
		switch(status) {
			case 0: {
				QFileInfo qfileinfo(disk->getAbsolutePath() + "/" + fullpath);
				disk->remove(File(fullpath));
				disk->remove(Folder(fullpath));
				break;
			}
			case 1:
				throw DaemonError(string("void Daemon::slotDelete(unsigned int status, QString& fullpath): cannot delete ")
													+ fullpath.toStdString()
													+ string(". Are you authenticated?."), status);
				break;
			case 2: {
				QFileInfo qfileinfo(disk->getAbsolutePath() + "/" + fullpath);
				if(qfileinfo.isFile())
					disk->remove(File(fullpath));
				else
					disk->remove(Folder(fullpath));
				throw DaemonError(string("void Daemon::slotDelete(unsigned int status, QString& fullpath): cannot delete ")
													+ fullpath.toStdString()
													+ string(". Server unknown error."), status);
				break;
			}
			default:
				throw DaemonError("void Daemon::slotDelete(unsigned int status, QString& fullpath): unknown exception", status);
				break;
		}
	}
	catch(DaemonError& e) {
		cerr << "DaemonError exception occured: " << e.what() << "Status: " << e.getStatus() << endl;
	}
	catch(FileError& e) {
		cerr << "FileError exception occured: " << e.what()
				 << e.getFile().getFullPath().toStdString() << " was not removed. " << endl;
	}
	catch(FolderError& e) {
		cerr << "FolderError exception occured: " << e.what()
				 << e.getFolder().getFullPath().toStdString() << " was not removed. " << endl;
	}
	catch(exception& e) {
		cerr << "Unknown exception occured: " << e.what() << endl;
	}
}



void Daemon::slotVersion(unsigned int status, const File&) {
	cout << "slotVersion() - status " << status << endl;
}

void Daemon::slotCopy(unsigned int status, QString path, const File& file) {
		try {
		switch(status) {
			case 0: {
				//FIXME
				break;
			}
			case 1:
				throw DaemonError(string("void Daemon::slotCopy(unsigned int status, QString path, const File& file): cannot copy ")
													+ file.getName().toStdString() + string(" to ")
													+ path.toStdString()
													+ string(". Are you authenticated?."), status);
				break;
			case 2:
				throw DaemonError(string("void Daemon::slotCopy(unsigned int status, QString path, const File& file): cannot copy ")
													+ file.getName().toStdString() + string(" to ")
													+ path.toStdString()
													+ string(". Server unknown error."), status);
				break;
			default:
				throw DaemonError("void Daemon::slotCopy(unsigned int status, QString path, const File& file): unknown exception", status);
		}
	}
	catch(DaemonError& e) {
		cerr << "DaemonError exception occured: " << e.what() << "Status: " << e.getStatus() << endl;
	}
		catch(exception& e) {
		cerr << "Unknown exception occured: " << e.what() << endl;
	}
}

void Daemon::slotCreateFolder(unsigned int status, const Folder& folder) {
	try {
		switch(status) {
			case 0: {
				askList(folder);
				break;
			}
			case 1:
				throw DaemonError("void Daemon::slotCreateFolder(unsigned int status, const Folder& folder): are you authenticated?", status);
			break;
			case 2:
				throw DaemonError("void Daemon::slotCreateFolder(unsigned int status, const Folder& folder): server unknown error.", status);
			break;
			default:
				throw DaemonError("void Daemon::slotCreateFolder(unsigned int status, const Folder& folder): unknown exception", status);
		}
	}
	catch(DaemonError& e) {
		cerr << "DaemonError exception occured: " << e.what() << "Status: " << e.getStatus() << endl;
	}
	catch(exception& e) {
		cerr << "Unknown exception occured: " << e.what() << endl;
	}
}

void Daemon::slotMove(unsigned int status, const QString& oldPath, const QString& newPath) {
	try {
		switch(status) {
			case 0:
				disk->move(oldPath, newPath);
				break;
			case 1:
				throw DaemonError(string("void Daemon::slotMove(unsigned int status, const QString& oldPath, const QString& newPath): cannot move ")
													+ oldPath.toStdString()
													+ string(" to ")
													+ newPath.toStdString()
													+ string(". Are you authenticated?"), status);
				break;
			case 2:
				throw DaemonError(string("void Daemon::slotMove(unsigned int status, const QString& oldPath, const QString& newPath): cannot move ")
													+ oldPath.toStdString()
													+ string(" to ")
													+ newPath.toStdString()
													+ string(". Server unknown error."), status);
				break;
			default:
				throw DaemonError("void Daemon::slotMove(unsigned int status, const QString& oldPath, const QString& newPath): unknown exception.", status);
				break;
		}
	}
	catch(DaemonError& e) {
		cerr << "DaemonError exception occured: " << e.what() << "Status: " << e.getStatus() << endl;
	}
	catch(FileError& e) {
		cerr << "FileError exception occured: " << e.what()
				 << e.getFile().getFullPath().toStdString() << " was not moved. " << endl;
	}
	catch(exception& e) {
		cerr << "Unknown exception occured: " << e.what() << endl;
	}
}

void Daemon::updateDisk() {
	cout << "Updating Disk" << endl;
	try {
		askList(Folder("/",""));
	}
	catch(DaemonError& e) {
		cerr << "DaemonError exception occured: " << e.what() << "Status: " << e.getStatus() << endl;
	}
	catch(FileSystemError& e) {
		cerr << "FileSystemError exception occured: " << e.what()
				 << disk->getAbsolutePath().toStdString() << " cannot be checked. " << endl;
	}
	catch(SenderError& e) {
		cerr << "SenderError exception occured: " << e.what()
				 << "XML: " << e.getXml().toStdString()	 << endl;
		//askList SenderError exception only happens if we are not connected,so :
		askLogin();
	}
	catch(exception& e) {
		cerr << "Unknown exception occured: " << e.what() << endl;
	}
}

}
